﻿using System;
using Microsoft.Xna.Framework.Graphics;
using Syngine.Components;
using Syngine.Content;
using Syngine.Input;
// ReSharper disable RedundantUsingDirective
using Syngine.Input.Touch;
// ReSharper restore RedundantUsingDirective
#if !(WINDOWS_PHONE || ANDROID)
using Syngine.Input.Mouse;
#endif

namespace Syngine.Controls
{
	/// <summary>
	/// 
	/// </summary>
	public abstract class Control : Drawable
	{
		#region Fields

		private bool _wasClicked;
		private float _doubleClickTimer;

		protected IInputAction ClickAction;
		protected IInputAction DoubleClickAction;
		protected IInputAction PressedAction;
#if !(WINDOWS_PHONE || ANDROID)
		protected IInputAction MouseOnAction;
		protected IInputAction MouseHoverAction;
		protected IInputAction MouseOffAction;
		protected IInputAction DragBeginAction;
		protected IInputAction DragingAction;
		protected IInputAction DragEndAction;
#endif

		#endregion

		#region Constructors

		protected Control() : this(null, null)
		{
		}

		protected Control(string theme) : this(theme, null)
		{
		}

		protected Control(string theme, string assetName)
		{
			Enabled = true;
			AssetName = assetName;
			StateManager = new ControlStateManager(this, theme, assetName);
			DoubleClickDuration = (float)TimeSpan.FromSeconds(1).TotalSeconds;
		}

		#endregion

		#region Properties

		#region Events

		/// <summary>
		/// Occurs when the control is clicked.
		/// </summary>
		public event Action<InputCallbackContext> Click;

		/// <summary>
		/// Occurs when the control is double clicked.
		/// </summary>
		public event Action<InputCallbackContext> DoubleClick;

		/// <summary>
		/// Occurs when the mouse is down on the control.
		/// </summary>
		public event Action<InputCallbackContext> MouseDown;

#if !(WINDOWS_PHONE || ANDROID)
		/// <summary>
		/// Occurs when [mouse on].
		/// </summary>
		public event Action<InputCallbackContext> MouseOn;

		/// <summary>
		/// Occurs when [mouse hover].
		/// </summary>
		public event Action<InputCallbackContext> MouseHover;

		/// <summary>
		/// Occurs when [mouse off].
		/// </summary>
		public event Action<InputCallbackContext> MouseOff;

		/// <summary>
		/// Occurs when [drag begin].
		/// </summary>
		public event Action<InputCallbackContext> DragBegin;

		/// <summary>
		/// Occurs when [dragging].
		/// </summary>
		public event Action<InputCallbackContext> Dragging;

		/// <summary>
		/// Occurs when [drag end].
		/// </summary>
		public event Action<InputCallbackContext> DragEnd;
#endif

		#endregion

		/// <summary>
		/// Gets or sets the duration of the double click.
		/// </summary>
		/// <value>
		/// The duration of the double click.
		/// </value>
		protected float DoubleClickDuration { get; set; }

		/// <summary>
		/// Gets or sets the name of the asset.
		/// </summary>
		/// <value>The name of the asset.</value>
		public string AssetName
		{
			get
			{
				return Texture.AssetName;
			}
			set
			{
				Texture = new Asset<Texture2D>(value);
			}
		}

		/// <summary>
		/// Gets or sets the text for this control.
		/// </summary>
		/// <value>The text for this control.</value>
		public string Text { get; set; }

		/// <summary>
		/// Gets or sets the font.
		/// </summary>
		/// <value>
		/// The font.
		/// </value>
		public Asset<SpriteFont> Font { get; set; }

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="Control"/> is enabled.
		/// </summary>
		/// <value>
		///   <c>true</c> if enabled; otherwise, <c>false</c>.
		/// </value>
		public bool Enabled { get; set; }
		
#if !(WINDOWS_PHONE || ANDROID)
		/// <summary>
		/// Gets or sets a value indicating whether this instance is draggable.
		/// </summary>
		/// <value>
		/// <c>true</c> if this instance is draggable; otherwise, <c>false</c>.
		/// </value>
		public bool IsDraggable { get; set; }
#endif

		/// <summary>
		/// Gets or sets the state of this control.
		/// </summary>
		/// <value>
		/// The state.
		/// </value>
		public ControlState State { get; set; }

		/// <summary>
		/// Gets or sets the text alignment.
		/// </summary>
		/// <value>
		/// The text alignment.
		/// </value>
		public Alignment TextAlignment { get; set; }

		/// <summary>
		/// Gets the state manager.
		/// </summary>
		/// <value>
		/// The state manager.
		/// </value>
		public ControlStateManager StateManager { get; private set; }

		#endregion

		#region Methods

		/// <summary>
		/// 
		/// </summary>
		public override void Initialize()
		{
#if !(WINDOWS_PHONE || ANDROID)
			PressedAction = Input.If(i => i.IsPressed(MouseButtons.Left, Bounds)).Call(OnMouseDown);
			ClickAction = Input.If(i => i.WasReleased(MouseButtons.Left, Bounds) || i.WasReleased(MouseButtons.Right, Bounds)).Call(OnClick);
			MouseOnAction = Input.If(i => !i.WasOver(Bounds) && i.IsOver(Bounds)).Call(OnMouseOn);
			MouseHoverAction = Input.If(i => i.IsOver(Bounds)).Call(OnMouseHover);
			MouseOffAction = Input.If(i => i.WasOver(Bounds)).Call(OnMouseOff);

			if (IsDraggable)
			{
				DragBeginAction = Input.If(i => !i.WasOver(Bounds) && i.IsOver(Bounds) && i.IsPressed(MouseButtons.Left)).Call(OnDragBegin);
				DragingAction = Input.If(i => i.IsOver(Bounds) && i.IsDown(MouseButtons.Left)).Call(OnDraging);
				DragEndAction = Input.If(i => i.IsOver(Bounds) && i.WasReleased(MouseButtons.Left)).Call(OnDragEnd);
			}
#else
			PressedAction = Input.If(i => i.IsPressed(Bounds)).Call(OnMouseDown);
			ClickAction = Input.If(i => i.WasReleased(Bounds) || i.WasReleased(Bounds)).Call(OnClick);
			//DragBeginAction = Input.If(i => !i.WasOver(Bounds) && i.IsOver(Bounds) && i.IsPressed(MouseButtons.Left)).Call(OnDragBegin);
			//DragingAction = Input.If(i => i.IsOver(Bounds) && i.IsDown(Bounds)).Call(OnDraging);
			//DragEndAction = Input.If(i => i.IsOver(Bounds) && i.WasReleased(MouseButtons.Left)).Call(OnDragEnd);
#endif

			base.Initialize();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		public override void LoadContent(LoadContext context)
		{
			if (!string.IsNullOrEmpty(AssetName))
			{
				base.LoadContent(context);
				StateManager.DefaultTexture = Texture;
			}
		}

		/// <summary>
		/// Loads the content for the given state.
		/// </summary>
		/// <param name="context">The context.</param>
		/// <param name="state">The state.</param>
		public virtual void LoadContent(LoadContext context, ControlState state)
		{
			StateManager.LoadTexture(context, state);
		}

		/// <summary>
		/// Loads the font with the provided asset name.
		/// </summary>
		/// <param name="context">The context.</param>
		/// <param name="assetName">Name of the asset.</param>
		public virtual void LoadFont(LoadContext context, string assetName)
		{
			Font = new Asset<SpriteFont>(assetName);
			Font.LoadContent(context);
		}

		/// <summary>
		/// Unloads the content.
		/// </summary>
		public override void UnloadContent()
		{
			ClickAction.Remove();
			DoubleClickAction.Remove();
#if !(WINDOWS_PHONE || ANDROID)
			MouseOnAction.Remove();
			MouseHoverAction.Remove();
			MouseOffAction.Remove();

			if (DragBeginAction != null)
			{
				DragBeginAction.Remove();
			}

			if (DragingAction != null)
			{
				DragingAction.Remove();
			}

			if (DragEndAction != null)
			{
				DragEndAction.Remove();
			}
#endif
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		public override void Update(UpdateContext context)
		{
			if (Enabled)
			{
				base.Update(context);
			}
		}

		/// <summary>
		/// Called when [click].
		/// </summary>
		/// <param name="context">The callback context.</param>
		public void OnClick(InputCallbackContext context)
		{
			State = ControlState.Clicked;
			StateManager.ApplyState(ControlState.Default);
			InvokeEvent(Click, context);

			if (_doubleClickTimer > 0.0f && _doubleClickTimer <= DoubleClickDuration)
			{
				OnDoubleClick(context);
			}

			_wasClicked = true;
		}

		/// <summary>
		/// Called when [double click].
		/// </summary>
		/// <param name="obj">The obj.</param>
		public virtual void OnDoubleClick(InputCallbackContext obj)
		{
			State = ControlState.DoubleClicked;
			StateManager.ApplyState(ControlState.DoubleClicked);
			InvokeEvent(DoubleClick, obj);
		}

		/// <summary>
		/// Called when [mouse down].
		/// </summary>
		/// <param name="obj">The obj.</param>
		public virtual void OnMouseDown(InputCallbackContext obj)
		{
			State = ControlState.Clicked;
			StateManager.ApplyState(ControlState.Clicked);
			InvokeEvent(MouseDown, obj);
		}

#if !(WINDOWS_PHONE || ANDROID)
		/// <summary>
		/// Called when [mouse on].
		/// </summary>
		/// <param name="obj">The obj.</param>
		public virtual void OnMouseOn(InputCallbackContext obj)
		{
			var state = State;

			if (state != ControlState.Clicked && state != ControlState.Hover)
			{
				State = ControlState.Hover;
				StateManager.ApplyState(ControlState.Hover);
			}

			InvokeEvent(MouseOn, obj);
		}

		/// <summary>
		/// Called when [mouse hover].
		/// </summary>
		/// <param name="obj">The obj.</param>
		public virtual void OnMouseHover(InputCallbackContext obj)
		{
			InvokeEvent(MouseHover, obj);
		}

		/// <summary>
		/// Called when [mouse off].
		/// </summary>
		/// <param name="obj">The obj.</param>
		public virtual void OnMouseOff(InputCallbackContext obj)
		{
			State = ControlState.Default;
			StateManager.ApplyState(ControlState.Default);
			InvokeEvent(MouseOff, obj);
		}

		/// <summary>
		/// Called when [drag begin].
		/// </summary>
		/// <param name="obj">The obj.</param>
		public virtual void OnDragBegin(InputCallbackContext obj)
		{
			if (IsDraggable)
			{
				State = ControlState.Dragging;
				StateManager.ApplyState(ControlState.Dragging);
				InvokeEvent(DragBegin, obj);
			}
		}

		/// <summary>
		/// Called when [draging].
		/// </summary>
		/// <param name="obj">The obj.</param>
		public virtual void OnDraging(InputCallbackContext obj)
		{
			if (IsDraggable)
			{
				InvokeEvent(Dragging, obj);
			}
		}

		/// <summary>
		/// Called when [drag end].
		/// </summary>
		/// <param name="obj">The obj.</param>
		public virtual void OnDragEnd(InputCallbackContext obj)
		{
			if (IsDraggable)
			{
				State = ControlState.Hover;
				StateManager.ApplyState(ControlState.Hover);
				InvokeEvent(DragEnd, obj);
			}
		}
#endif

		/// <summary>
		/// Invokes the event.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="action">The action.</param>
		/// <param name="item">The item.</param>
		protected virtual void InvokeEvent<T>(Action<T> action, T item)
		{
			if (action != null)
			{
				action(item);
			}
		}

		protected override void UpdateCore(UpdateContext context)
		{
			base.UpdateCore(context);

			if (_wasClicked)
			{
				_doubleClickTimer += (float)context.GameTime.ElapsedGameTime.TotalSeconds;

				if (_doubleClickTimer >= DoubleClickDuration)
				{
					_wasClicked = false;
					_doubleClickTimer = 0.0f;
				}
			}
		}

		#endregion
	}
}